//
// Copyright (c) 2009 Tridium, Inc.
// Licensed under the Academic Free License version 3.0
//
// History:
//   03 June 09  Matthew Giannini  Creation
//
package sedonac.steps;

import java.io.*;
import java.util.*;

import sedona.Env;
import sedona.platform.*;
import sedona.xml.*;
import sedonac.Compiler;
import sedonac.*;
import sedonac.ir.*;
import sedonac.namespace.*;
import sedonac.platform.*;
import sedonac.util.*;

/**
 * Creates and stages the platform manifest and sedonaPlatform.h header file.
 */
public class StagePlatform extends CompilerStep
{
  public StagePlatform(Compiler compiler)
  {
    super(compiler);
  }

  public void run()
  {
    log.info("  StagePlatform");
    makePlatformManifest(compiler.platform);
    writeCHeaders();
    writeManifest();
    quitIfErrors();
  }
  
  private void makePlatformManifest(PlatformDef platDef)
  {
    // platformManifest root attributes
    manifest = new PlatformManifest();
    manifest.id         = platDef.idPattern;
    manifest.vendor     = platDef.vendor;
    manifest.endian     = platDef.endian;
    manifest.blockSize  = platDef.blockSize;
    manifest.refSize    = platDef.refSize;
    manifest.armDouble  = platDef.armDouble;
    manifest.debug      = platDef.debug;
    manifest.test       = platDef.test;
    
    // native kits
    manifest.nativeKits = platDef.nativeKits;
    
    // native methods
    Slot[] nativeMethods = findNativeMethods();
    manifest.nativeMethods = new NativeManifest[nativeMethods.length];
    for (int i=0; i<nativeMethods.length; ++i)
      manifest.nativeMethods[i] = toNativeManifest(manifest, nativeMethods[i]);
    Arrays.sort(manifest.nativeMethods, new NativeComparator());
    
    // manifest includes
    for (int i=0; i<platDef.manifestIncludes.length; ++i)
      manifest.manifestIncludes.addContent(platDef.manifestIncludes[i]);
    
    resolvePlatformId();
  }
  
  private Slot[] findNativeMethods()
  {
    ArrayList natives = new ArrayList();
    for (int i=0; i<compiler.kits.length; ++i)
    {
      IrKit kit = compiler.kits[i];
      if (!kit.manifest.hasNatives) continue;
      for (int j=0; j<kit.types.length; ++j)
      {
        Slot[] slots = kit.types[j].slots();
        for (int k=0; k<slots.length; ++k)
        {
          if (slots[k].isNative()) natives.add(slots[k]);
        }
      }
    }
    return (Slot[])natives.toArray(new Slot[natives.size()]);
  }
  
  private NativeManifest toNativeManifest(PlatformManifest manifest, Slot nativeSlot)
  {
    IrMethod method = (IrMethod)nativeSlot;
    return new NativeManifest(manifest, method.qname(), method.nativeId.string);
  }
  
  private void resolvePlatformId()
  {
    if (manifest.id == null) return;
    
    Properties vars = new Properties();
    vars.put("stage.nativeChecksum", calcNativeCksum());
    try
    {
      manifest.id = (new VarResolver(vars)).resolve(manifest.id);
    }
    catch (Exception e)
    {
      throw err(e.getMessage(), "id attribute");
    }
  }
  
  private String calcNativeCksum()
  {
    // Assumes natives have already been ordered.
    java.util.zip.CRC32 crc = new java.util.zip.CRC32();
    for (int i=0; i<manifest.nativeMethods.length; ++i)
    {
      NativeManifest nm = manifest.nativeMethods[i];
      crc.update(nm.qname.getBytes());
      crc.update(nm.nativeId.getBytes());
    }
    return Integer.toHexString((int)crc.getValue());
  }
  
  private void writeCHeaders()
  {
    if (manifest.id == null) return;
    
    File file = new File(compiler.outDir, "sedonaPlatform.h");
    log.debug("    Writing [ " + file + " ]");
    try
    {
      PrintWriter out = new PrintWriter(new FileWriter(file));
      out.println("//");
      out.println("// Generated by sedonac " + Env.version);
      out.println("// " + Env.timestamp());
      out.println("//");
      out.println();
      out.println("#ifndef SEDONAC_SEDONA_PLATFORM_H");
      out.println("#define SEDONAC_SEDONA_PLATFORM_H");
      out.println();
      out.println("#define PLATFORM_ID \"" + manifest.id + "\"");
      out.println();
      out.println("#endif");
      out.close();
    }
    catch (Exception e)
    {
      throw err("Cannot write sedonaPlatform.h", new Location(file), e);
    }
  }
  
  private void writeManifest()
  {
    File parDir = new File(compiler.outDir, ".par");
    File file = new File(parDir, "platformManifest.xml");
    log.debug("    Writing [ " + file + " ]");
    try
    {
      XWriter out = new XWriter(file);
      manifest.encodeXml(out);
      out.close();
    }
    catch (Exception e)
    {
      throw err("Cannot write platformManifest.xml", new Location(file), e);
    }
  }
  
  private class NativeComparator implements Comparator
  {
    public int compare(Object o1, Object o2)
    {
      NativeId id1 = NativeId.parse(null, ((NativeManifest)o1).nativeId);
      NativeId id2 = NativeId.parse(null, ((NativeManifest)o2).nativeId);
      if (id1.equals(id2)) throw new IllegalStateException("Duplicate nativeIds: " + id1 + " " + id2);
      final int kitCompare = id1.kitId - id2.kitId;
      return (kitCompare == 0) ? id1.methodId - id2.methodId : kitCompare;
    }
  }
  
  PlatformManifest manifest;

}
